package com.gitee.apanlh.util.algorithm.digest;

import com.gitee.apanlh.exp.DigestException;
import com.gitee.apanlh.util.base.Empty;
import com.gitee.apanlh.util.base.Eq;
import com.gitee.apanlh.util.random.RandomUtils;
import com.gitee.apanlh.util.valid.Assert;
import com.gitee.apanlh.util.valid.ValidParam;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;

/**	
 * 	HMac(消息认证码)摘要算法
 * 	<br>支持常用的HMac摘要算法
 * 	<br>可以生成HMac摘要值，并提供相关操作方法
 * 	<br>包括自动生成密钥、获取密钥以字节形式返回、获取消息认证码(MAC)的长度等
 * 	<br>默认使用HMacSHA256摘要算法
 * 	<br>可通过getKey()方法获取密钥
 * 	<br>可通过getKeyBytes()方法获取字节密钥
 *  <br>可通过getMacLength()方法获取消息认证码(MAC)的长度
 *  	
 * 	@author Pan
 */
public class HMac extends JdkDigestAbstract {
	
	/** HMac密钥 */
	private Key key;
	/** HMac生成器 */
	private Mac mac;
	
	/**
	 * 	默认构造函数
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	
	 * 	@author Pan
	 */
	public HMac() {
		this(Empty.arrayByte(), DigestType.HMAC_SHA256);
	}
	
	/**
	 * 	构造函数
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>默认生成密钥(消息认证码)，如果需要获取使用{@link #getKey()}
	 * 	
	 * 	@author Pan
	 * 	@param 	digestType		摘要类型
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public HMac(DigestType digestType) {
		this(Empty.arrayByte(), digestType);
	}
	
	/**
	 * 	构造函数
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKey		密钥
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public HMac(byte[] secretKey) {
		this(secretKey, DigestType.HMAC_SHA256);
	}
	
	/**
	 * 	构造函数
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKey		密钥
	 * 	@param 	digestType		摘要类型
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public HMac(byte[] secretKey, DigestType digestType) {
		setDigestType(digestType);
		check();
		initKey(secretKey);
	}
	
	/**
	 * 	构造函数
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	<br>自定义指定字符串HMac密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKeyStr	字符串密钥
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public HMac(String secretKeyStr) {
		this(secretKeyStr, DigestType.HMAC_SHA256);
	}
	
	/**
	 * 	构造函数
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKeyStr	字符串密钥
	 * 	@param 	digestType		摘要类型
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public HMac(String secretKeyStr, DigestType digestType) {
		Assert.isNotEmpty(secretKeyStr);
		
		setDigestType(digestType);
		check();
		initKey(secretKeyStr.getBytes());
	}

	@Override
	public byte[] hash(byte[] content) {
		return this.mac.doFinal(content);
	} 
	
	/**	
	 * 	初始化密钥
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKey		自定义密钥
	 * 	@throws DigestException 如果找不到对应摘要算法或初始化密钥出错则抛出
	 */
	private void initKey(byte[] secretKey) {
		try {
			this.mac = Mac.getInstance(getDigestType().getAlgorithm());
		} catch (Exception e) {
			throw new DigestException(e.getMessage());
		}
		
		//	如果密钥值为空将自动生成密钥值
		if (ValidParam.isEmpty(secretKey)) {
			// 生成随机的密钥字节数组
			this.key = new SecretKeySpec(RandomUtils.randomBytes(mac.getMacLength(), true), getDigestType().getAlgorithm());
		} else {
			this.key = new SecretKeySpec(secretKey, getDigestType().getAlgorithm());
		}
		
		//	初始化密钥
		try {
			mac.init(key);
		} catch (Exception e) {
			throw new DigestException(e.getMessage(), e);
		}
	}
	
	@Override
	public void check() {
		if (!Eq.enumsOr(getDigestType(), 
				DigestType.HMAC_MD5,
				DigestType.HMAC_SHA1, 
				DigestType.HMAC_SHA224, 
				DigestType.HMAC_SHA256, 
				DigestType.HMAC_SHA384, 
				DigestType.HMAC_SHA512)) {
			throw new DigestException("digest type error cause: expected type[{}] but actual type is[{}]", "HMacMD5/HMacSha1/HMacSha224/HMacSha256/HMacSha384/HMacSha512", getDigestType().getAlgorithm());
		}
	}
	
	/**	
	 * 	获取HMac密钥
	 * 	
	 * 	@author Pan
	 * 	@return	Key
	 */
	public Key getKey() {
		return this.key;
	}
	
	/**	
	 * 	获取HMac密钥以字节形式返回
	 * 	<br>返回的字节数组可以通过适当的编码和解码方式转换为不同的格式
	 * 	<br>例如Base64编码或十六进制表示，以便将密钥转换为字符串或其他方式进行处理
	 * 
	 * 	@author Pan
	 * 	@return	byte[]
	 */
	public byte[] getKeyBytes() {
		return this.key.getEncoded();
	}
	
	/**	
	 * 	获取消息认证码(MAC)的长度。
	 * 
	 * 	@author Pan
	 * 	@return	int
	 */
	public int getMacLength() {
		return this.mac.getMacLength();
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	
	 * 	@author Pan
	 * 	@return	HMac
	 */
	public static HMac create() {
		return new HMac();
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>默认生成密钥(消息认证码)，如果需要获取使用{@link #getKey()}
	 * 	
	 * 	@author Pan
	 * 	@param 	digestType		摘要类型
	 * 	@return	HMac
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public static HMac create(DigestType digestType) {
		return new HMac(digestType);
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKey		密钥
	 * 	@return	HMac
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public static HMac create(byte[] secretKey) {
		return new HMac(secretKey);
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKey		密钥
	 * 	@param 	digestType		摘要类型
	 * 	@return	HMac
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public static HMac create(byte[] secretKey, DigestType digestType) {
		return new HMac(secretKey, digestType);
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>默认使用HMacSHA256摘要算法
	 * 	<br>自定义指定字符串HMac密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKeyStr	字符串密钥
	 * 	@return	HMac
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public static HMac create(String secretKeyStr) {
		return new HMac(secretKeyStr);
	}
	
	/**
	 * 	构建HMac摘要算法
	 * 	<br>自定义指定HMac摘要类型
	 * 	<br>自定义密钥
	 * 	
	 * 	@author Pan
	 * 	@param 	secretKeyStr	字符串密钥
	 * 	@param 	digestType		摘要类型
	 * 	@return	HMac
	 * 	@throws DigestException 如果未找到对应摘要算法则抛出
	 */
	public static HMac create(String secretKeyStr, DigestType digestType) {
		return new HMac(secretKeyStr, digestType);
	}
}
